using System;
using System.Reflection;
using Castle.Core.Interceptor;
using System.Collections.Generic;

namespace Duhking
{
    public class DuhkingInterceptor : IInterceptor
    {
        readonly WeakReference _objRef;
        readonly Type _targetObjType;
        private readonly Dictionary<MethodInfo, Delegate> _methodStore;
        private readonly InterfaceDuhker _duhker;


        public DuhkingInterceptor(object target, InterfaceDuhker duhker)
        {
            _objRef = new WeakReference(target);
            _targetObjType = target.GetType();
            _methodStore = new Dictionary<MethodInfo, Delegate>();
            _duhker = duhker;
        }

        public void Intercept(IInvocation invocation)
        {
            if (!_objRef.IsAlive)
                throw new ObjectDisposedException(_targetObjType.FullName);

            Delegate methodExecuter = GetTargetMethod(invocation.Method, invocation.GenericArguments);
            if (invocation.Method.ReturnType != typeof(void))
                invocation.ReturnValue = ((LateBoundMethod)methodExecuter)(_objRef.Target, invocation.Arguments);
            else
                ((VoidLateBoundMethod)methodExecuter)(_objRef.Target, invocation.Arguments);
        }

        private Delegate GetTargetMethod(MethodInfo methodInfo, Type[] genericArguments)
        {
            if (!_methodStore.ContainsKey(methodInfo))
                BuildMethod(methodInfo, genericArguments);
            return _methodStore[methodInfo];
        }

        private void BuildMethod(MethodInfo methodInfo, Type[] genericArguments)
        {
            MethodInfo targetMethod = GetTargetMethod(methodInfo);

            if (MethodIsGeneric(genericArguments))
                HandleGenericDuhk(genericArguments, ref targetMethod);
            if (ReturnTypeShouldBeDuhked(methodInfo, targetMethod))
                HandleNestedDuhk(methodInfo, targetMethod);
            else
                HandleStandardDuhk(methodInfo, targetMethod);
        }

        private bool MethodIsGeneric(Type[] genericArguments)
        {
            return genericArguments != null && genericArguments.Length > 0;
        }

        private bool ReturnTypeShouldBeDuhked(MethodInfo ifaceMethod,MethodInfo targetMethod)
        {
            return ifaceMethod.ReturnType.IsInterface && new InterfaceDuhker().CanDuhk(ifaceMethod.ReturnType, targetMethod.ReturnType);
        }

        private MethodInfo GetTargetMethod(MethodInfo methodInfo)
        {
            IFindMethod finder = GetFinder(methodInfo);

            MethodInfo targetMethod = finder.FindMatchingMethod(methodInfo, _targetObjType);
            return targetMethod;
        }
        private IFindMethod GetFinder(MethodInfo methodInfo)
        {
            return FinderFactory.GetFinder(methodInfo);
        }

        private static void HandleGenericDuhk(Type[] genericArguments, ref MethodInfo targetMethod)
        {
            targetMethod = targetMethod.MakeGenericMethod(genericArguments);
        }

        private void HandleNestedDuhk(MethodInfo methodInfo, MethodInfo targetMethod)
        {
            _methodStore.Add(methodInfo, targetMethod.CreateDuhkingExecuter(methodInfo.ReturnType,_duhker));
        }

        private void HandleStandardDuhk(MethodInfo methodInfo, MethodInfo targetMethod)
        {
            _methodStore.Add(methodInfo, targetMethod.CreateMethodExecuterDelegate());
        }
    }
}
